import { Matrix4, Vector2, degToRad } from './_imports'
import {
  getMatchRatioAndCount,
  getBounds,
  sweepPolygonAlongX,
  sSample,
  getValidIndexes,
} from './math'

import type { MagizTypes, StyleParsed, Temp } from './_imports'

export {
  TEMP,
  applyBasicTransform,
  handleBoxes,
  pushBoxData,
  edgeUnitToTempBoxes,
  edgeFlexToTempBoxes,
  matchUnitsToTempBoxRows,
}

/** 计算过程中的缓存矩阵 */
const TEMP = new Matrix4()

function edgeUnitToTempBoxes(
  params: { boxes: StyleParsed.Box[]; flexes: StyleParsed.FlexVertical[] },
  xRatio: number
): Temp.Box[] {
  const result: Temp.Box[] = []
  boxesToTempBoxes(result, params.boxes, xRatio)
  FlexVerticalsToTempBoxes(result, params.flexes, xRatio)
  return result

  function boxesToTempBoxes(result: Temp.Box[], params: StyleParsed.Box[], xRatio: number) {
    params.forEach((boxParams) => {
      let { widthX, depthY, heightZ } = boxParams
      if (widthX || depthY || heightZ) {
        const matrix = new Matrix4().makeTranslation(0, 0, 0.5)
        if (widthX < 0) widthX = -widthX
        if (depthY < 0) depthY = -depthY
        if (heightZ < 0) {
          matrix.premultiply(TEMP.makeTranslation(0, 0, -1))
          heightZ = -heightZ
        }
        matrix.premultiply(TEMP.makeScale(widthX * xRatio, depthY, heightZ))
        applyBasicTransform(boxParams, matrix)
        result.push({ matrix, color: boxParams.color })
      }
    })
  }

  function FlexVerticalsToTempBoxes(
    result: Temp.Box[],
    params: StyleParsed.FlexVertical[],
    xRatio: number
  ) {
    params.forEach((flexParams) => {
      const { unitHeightZ, totalHeightZ, depthY, widthX, dash, shrink, seg } = flexParams

      /**竖向拟合 boxDashedArray 的结果 */
      const flexResult = getFlexResult(totalHeightZ, [unitHeightZ], seg, dash, shrink)

      // 将flexResult转为矩阵和颜色数据，推送到结果
      flexResult.forEach(({ move, space }) => {
        const matrix = new Matrix4()
          .makeTranslation(0, 0, 0.5)
          .premultiply(TEMP.makeScale(widthX * xRatio, depthY, space))
        applyBasicTransform(flexParams, matrix)
        matrix.premultiply(TEMP.makeTranslation(0, 0, move))
        result.push({ matrix, color: flexParams.color })
      })
    })
  }
}

function edgeFlexToTempBoxes(params: StyleParsed.EdgeFlex, distance: number) {
  const result: Temp.Box[] = []
  const { flexDepth, flexHeight, array, seg, ctrlFlex, shrink } = params
  const flexResult = getFlexResult(distance, array, seg, ctrlFlex, shrink)

  // 将flexResult转为矩阵和颜色数据
  flexResult.forEach((u) => {
    const matrix = new Matrix4()
      .makeTranslation(0.5, 0, flexHeight > 0 ? 0.5 : -0.5)
      .premultiply(TEMP.makeScale(u.space, Math.abs(flexDepth), Math.abs(flexHeight)))
    applyBasicTransform(params, matrix)
    matrix.premultiply(TEMP.makeTranslation(u.move, 0, 0))
    result.push({ matrix, color: params.color })
  })

  return result
}

/** 相对y坐标，考虑将相交的结果推送到saveAs */
function pushSweepFromY(
  saveAs: Temp.LineSweepX[][],
  y: number,
  matchUnit: StyleParsed.FlexMatchUnit,
  lines: Temp.Line[]
) {
  saveAs.push(
    sweepPolygonAlongX(y + matchUnit.unitDepth / 2, lines).map((pair) => {
      return Object.assign(pair, {
        depth: matchUnit.unitDepth,
        height: matchUnit.flexHeight,
        shrink: matchUnit.shrink,
        trans: matchUnit.trans,
        color: matchUnit.color,
        stateJSON:
          JSON.stringify(matchUnit.trans) +
          JSON.stringify(matchUnit.color) +
          JSON.stringify(matchUnit.shrink),
      })
    })
  )
}

function matchUnitsToTempBoxRows(
  params: StyleParsed.FlexMatchUnit[],
  lines: Temp.Line[],
  sandwich: StyleParsed.Bool,
  simplify: StyleParsed.Bool
): Temp.Box[][] {
  /** 按参数拟合平面，结果保存为 Temp.LineSweepX[][] */
  const result: Temp.Box[][] = []
  const bounds = getBounds(lines.map((line) => line.start))
  if (bounds) {
    /** 处理matchUnit.count, 生成间距序列 */
    const depths: number[] = []
    params.forEach((matchUnit) => {
      for (let i = 0; i < matchUnit.count; i++) {
        depths.push(matchUnit.unitDepth)
      }
    })

    // 计算沿Y轴的拟合次数和比例
    const rc = getMatchRatioAndCount(depths, bounds.max.y - bounds.min.y, 0, sandwich)

    if (rc) {
      /** 缓存拟合单元及按拟合的比例重新生成的拟合序列 */
      const sweepArrayScaled: StyleParsed.FlexMatchUnit[] = []
      params.forEach((matchUnit) => {
        for (let i = 0; i < matchUnit.count; i++) {
          const unitClone = { ...matchUnit }
          unitClone.unitDepth *= rc.ratio
          sweepArrayScaled.push(unitClone)
        }
      })
      // 计算沿X轴与平面重合的线段
      let sweepResult: Temp.LineSweepX[][] = []
      let y = bounds.min.y
      for (let i = 0; i < rc.count; i++) {
        sweepArrayScaled.forEach((unitScaled) => {
          pushSweepFromY(sweepResult, y, unitScaled, lines)
          y += unitScaled.unitDepth
        })
      }

      if (sandwich) {
        pushSweepFromY(sweepResult, y, sweepArrayScaled[0]!, lines)
      }

      if (simplify) sweepResult = simplifySweepX(sweepResult)

      sweepResult.forEach((row) => {
        const newRow: Temp.Box[] = []
        row.map((lineSweepX) => {
          const { start, end, height, color, shrink } = lineSweepX
          const mtx = new Matrix4()
            .makeTranslation(0.5, 0, height > 0 ? 0.5 : -0.5)
            .premultiply(TEMP.makeScale(1, lineSweepX.depth, Math.abs(height)))
          indentBoxFlexWidth(mtx, shrink, Math.abs(start.x - end.x)).forEach((matrix) => {
            applyBasicTransform(lineSweepX, matrix)
            matrix.premultiply(TEMP.makeTranslation(start.x, start.y, 0))
            newRow.push({ matrix, color })
          })
        })
        result.push(newRow)
      })
    }
  }
  return result
}

function simplifySweepX(rowsSweepX: Temp.LineSweepX[][]) {
  /** 因浮点计算，须低精度比较大小 */
  function isSame(a: number, b: number) {
    return Math.abs(a - b) < 0.0001
  }

  const result: Temp.LineSweepX[][] = []
  rowsSweepX.forEach((newRow, i) => {
    if (i === 0) {
      result.push(newRow)
    } else {
      const thisRow: Temp.LineSweepX[] = []
      const lastRow = result[result.length - 1]!

      for (let i = 0; i < newRow.length; i++) {
        const thisLine = newRow[i]
        const lastLine = lastRow[i]

        if (thisLine) {
          if (
            lastLine &&
            isSame(lastLine.start.x, thisLine.start.x) &&
            isSame(lastLine.end.x, thisLine.end.x) &&
            isSame(lastLine.height, thisLine.height) &&
            lastLine.stateJSON === thisLine.stateJSON
          ) {
            // 可合并的，将数值累加到lastLine
            const addMoveY = thisLine.depth / 2
            lastLine.start.y += addMoveY
            lastLine.end.y += addMoveY
            lastLine.depth += thisLine.depth
          } else {
            thisRow.push(thisLine)
          }
        }

        if (thisRow.length > 0) result.push(thisRow)
      }

      // newRow.forEach((line) => {
      //   const thisRow: Temp.LineSweepX[] = []
      //   const same = lastRow.find(
      //     (lastLine) =>
      //       isSame(lastLine.start.x, line.start.x) &&
      //       isSame(lastLine.end.x, line.end.x) &&
      //       isSame(lastLine.matchUnit.flexHeight, line.matchUnit.flexHeight) &&
      //       lastLine.stateJSON === line.stateJSON
      //   )

      //   if (same) {
      //     const moveY = line.depth / 2
      //     same.start.y += moveY
      //     same.end.y += moveY
      //     same.depth += line.depth
      //   } else {
      //     thisRow.push(line)
      //   }
      //   if (thisRow.length > 0) result.push(thisRow)
      // })
    }
  })

  return result
}

function handleBoxes(
  /** 推送可序列化数据到结果 */
  result: MagizTypes.DataJSON.ModelData,
  /** 参数 */
  boxes: StyleParsed.Box[],
  /** 在每个标高生成一次 */
  elevations: number[],
  /** 生成的位置 */
  point: Vector2
) {
  boxes.forEach((box) => {
    const mtx = new Matrix4()
      .makeTranslation(0, 0, 0.5)
      .premultiply(TEMP.makeScale(box.widthX, box.depthY, box.heightZ))
    applyBasicTransform(box, mtx)

    elevations.forEach((elevation) => {
      pushBoxData(
        result,
        box.color,
        mtx.premultiply(TEMP.makeTranslation(point.x, point.y, elevation))
      )
    })
  })
}

/** 将 instancedBox 数据推送到结果 */
function pushBoxData(
  saveAs: MagizTypes.DataJSON.ModelData,
  color: StyleParsed.ColorDataType[],
  matrix: Matrix4
) {
  const { index, glass } = sSample(color)!
  const target: MagizTypes.DataJSON.InstancedData = saveAs.instanced[glass ? 'boxGlass' : 'box']
  target.colors.push(index)
  target.matrices.push(matrix.toArray())
}

/** 按生成的flexWidth偏移boxFlex，因indent可能生成两段 */
function indentBoxFlexWidth(
  matrix: Matrix4,
  params: StyleParsed.IndentType | undefined,
  width: number
): Matrix4[] {
  const result: Matrix4[] = []
  if (params) {
    const { central, asRatio, reverse, start, end } = params
    const wStart = asRatio ? width * start : start
    const wEnd = asRatio ? width * end : end
    if (reverse) {
      if (start) {
        const w = central ? width / 2 - wStart : wStart
        if (w > 0) result.push(matrix.clone().premultiply(TEMP.makeScale(w, 1, 1)))
      }
      if (end < width - start) {
        const w = central ? width / 2 - wEnd : wEnd
        const x = central ? width / 2 + wEnd : width - wEnd
        if (w > 0)
          result.push(
            matrix
              .clone()
              .premultiply(TEMP.makeScale(w, 1, 1))
              .premultiply(TEMP.makeTranslation(x, 0, 0))
          )
      }
    } else {
      const w = central ? wStart + wEnd : width - wStart - wEnd
      if (w > 0) {
        matrix.premultiply(TEMP.makeScale(w, 1, 1))
        if (start) {
          matrix.premultiply(TEMP.makeTranslation(central ? width / 2 - wStart : wStart, 0, 0))
        }
        result.push(matrix)
      }
    }
  } else {
    result.push(matrix.premultiply(TEMP.makeScale(width, 1, 1)))
  }
  return result
}

/** 应用 StyleParsed.status.transform 到 matrix */
function applyBasicTransform(status: StyleParsed.Status, matrix: Matrix4): void {
  status.trans?.forEach((transform) => {
    if ('rotateX' in transform) {
      matrix.premultiply(TEMP.makeRotationX(degToRad(transform.rotateX)))
    } else if ('rotateY' in transform) {
      matrix.premultiply(TEMP.makeRotationY(degToRad(transform.rotateY)))
    } else if ('rotateZ' in transform) {
      matrix.premultiply(TEMP.makeRotationZ(degToRad(transform.rotateZ)))
    } else {
      matrix.premultiply(TEMP.makeTranslation(transform.moveX, transform.moveY, transform.moveZ))
    }
  })
}

/** 计算横向和竖向的拟合结果 */
function getFlexResult(
  distance: number,
  spaceArray: number[],
  asSegments: StyleParsed.Bool,
  control?: StyleParsed.IndexController,
  shrink?: StyleParsed.IndentType
) {
  const flexResult: { move: number; space: number }[] = []

  // console.log(spaceArray, distance, distance / spaceArray[0])

  // 竖向计算时为楼层生成方式，一般按层高，sandwich和endWidth可以忽略。水平计算时为立面生成方式，这两个参数才有用。考虑getMatchRatioAndCount的计算逻辑，可以完全通过sandwich控制
  const rc = getMatchRatioAndCount(spaceArray, distance, 0, 0)
  if (rc) {
    const validIndexes = getValidIndexes(rc.count * spaceArray.length, control)
    const totalCount = rc.count * spaceArray.length
    const spacesScaled = spaceArray.map((s) => s * rc.ratio)

    // 计算每段的相对起点和长度
    let move = 0
    let lastIndex = -1
    for (let i = 0; i < totalCount; i++) {
      const spaceScaled = spacesScaled[i % spaceArray.length]!
      lastIndex = generateEdgeFlex(
        lastIndex,
        i,
        move,
        spaceScaled,
        asSegments,
        validIndexes,
        flexResult
      )
      move += spaceScaled
    }
  }

  // 处理shrink，reverse和central可能产生新的段落
  if (shrink) {
    const result: { move: number; space: number }[] = []
    const { start, end, asRatio, central, reverse } = shrink
    flexResult.forEach((u) => {
      if (reverse) {
        // start, end 表示保留
        if (central) {
          // 反向时保留中部
          if (start) {
            const startD = u.space / 2 - (asRatio ? u.space * start : start)
            u.move += startD
            u.space -= startD
          }
          if (end) {
            const endD = u.space / 2 - (asRatio ? u.space * end : end)
            u.space -= endD
          }
          result.push(u)
        } else {
          // 反向时保留两端为两段
          if (start) {
            result.push({
              move: u.move,
              space: asRatio ? u.space * start : start,
            })
          }
          if (end) {
            const space = asRatio ? u.space * end : end
            result.push({ move: u.move + u.space - space, space })
          }
        }
      } else {
        // start, end 表示剔除
        if (central) {
          // 从中间向两端剔除为两段
          const half = u.space / 2
          const startPart = { move: u.move, space: half }
          const endPart = { move: u.move + half, space: half }
          if (start) {
            startPart.space -= asRatio ? u.space * start : start
          }
          if (end) {
            const endD = asRatio ? u.space * end : end
            endPart.space -= endD
            endPart.move += endD
          }
          result.push(startPart)
          result.push(endPart)
        } else {
          // 剔除两端
          if (start) {
            const startD = asRatio ? u.space * start : start
            u.move += startD
            u.space -= startD
          }
          if (end) {
            const endD = asRatio ? u.space * end : end
            u.space -= endD
          }
          result.push(u)
        }
      }
    })

    // 排除可能产生的非正向段落
    return result.filter((r) => r.space > 0)
  } else {
    return flexResult
  }
}

/** 用于循环调用的数据生成函数，返回上一个有效的序号 lastIndex */
function generateEdgeFlex(
  lastIndex: number,
  thisIndex: number,
  move: number,
  space: number,
  asSegments: StyleParsed.Bool,
  validIndexes: number[],
  result: { move: number; space: number }[]
): number {
  // 先验证 thisIndex 是否需要生成
  if (validIndexes.find((vi) => vi === thisIndex) !== undefined) {
    if (result.length === 0) {
      // 直接推送第一段到结果
      result.push({ move, space })
    } else {
      if (!asSegments && lastIndex === thisIndex - 1) {
        // 如果需要合并且前后相邻，将间距累加到之前
        result[result.length - 1]!.space += space
      } else {
        // 反之推送到结果
        result.push({ move, space })
      }
    }
    // 记录有效的序号，用于下一次判断是否需要合并
    lastIndex = thisIndex
  }
  return lastIndex
}
